//  -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	Smart.Common
//  File name:	CacheEntry.cs
//  Developer:	Solomatov Igor
//  Created:	15.04.2012
// 
//  -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.Web.Caching;

namespace Smart.Common
{
	/// <summary>
	/// Cache dictionary entry in SharePoint Cache. Multi-thread, Multi-Core aware.
	/// </summary>
	public class CacheEntry<TKeyType, TValueType> : CacheEntry<SyncDictionary<TKeyType, TValueType>>
	{
		/// <summary>
		/// Default item value
		/// </summary>
		public TValueType DefaultValue;

		/// <summary>
		/// Cache dictionary entry in SharePoint Cache. Multi-thread, Multi-Core aware.
		/// </summary>
		public CacheEntry(string key)
			: base(key)
		{
			Default = null;
		}

		/// <summary>
		/// Cache dictionary entry in SharePoint Cache. Multi-thread, Multi-Core aware.
		/// </summary>
		public CacheEntry(string prefix, string key)
			: base(prefix, key)
		{
			Default = null;
		}

		protected override SyncDictionary<TKeyType, TValueType> GetOrLoadDefault()
		{
			return
				typeof(TKeyType) == typeof(string)
					? new SyncDictionary<TKeyType, TValueType>((IEqualityComparer<TKeyType>)StringComparer.OrdinalIgnoreCase)
					: new SyncDictionary<TKeyType, TValueType>();
		}

		/// <summary>
		/// Get or Set item value
		/// </summary>
		public TValueType this[TKeyType itemKey]
		{
			get { return Get(itemKey); }
			set { Set(itemKey, value); }
		}

		/// <summary>
		/// Remove item value
		/// </summary>
		public bool Remove(TKeyType itemKey)
		{
			var d = Value;

			if (d == null)
			{
				return true;
			}

			var removed = d.Remove(itemKey);

			if (removed && IsSlidingExpiration)
				Update();

			return removed;
		}

		/// <summary>
		/// Get item value
		/// </summary>
		public TValueType Get(TKeyType itemKey)
		{
			var d = Value;

			if (d == null)
			{
				return DefaultValue;
			}

			TValueType res;

			return
				!d.TryGetValue(itemKey, out res)
					? DefaultValue
					: res;
		}

		/// <summary>
		/// Set item value
		/// </summary>
		public void Set(TKeyType itemKey, TValueType value)
		{
			var d = Value;

			if (d != null)
				d[itemKey] = value;

			if (IsSlidingExpiration)
				Update();
		}

		/// <summary>
		/// Checks item value exists
		/// </summary>
		public bool ContainsKey(TKeyType itemKey)
		{
			var res = false;

			var d = Value;

			if (d != null)
				res = d.ContainsKey(itemKey);

			return res;
		}

		/// <summary>
		/// Return Keys collection
		/// </summary>
		public ICollection<TKeyType> Keys
		{
			get
			{
				var d = Value;

				return (d == null ? null : d.Keys);
			}
		}

		/// <summary>
		/// Return Values collection
		/// </summary>
		public ICollection<TValueType> Values
		{
			get
			{
				var d = Value;

				return (d == null ? null : d.Values);
			}
		}
	}

	/// <summary>
	/// Cache entry in my SharePoint cache. Multi-thread, Multi-Core aware.
	/// </summary>
	public class CacheEntry<TValueType>
	{
		/// <summary>
		/// Cache keys
		/// </summary>
		public object[] PreKeys { get; set; }

		/// <summary>
		/// Cache post keys
		/// </summary>
		public object[] PostKeys { get; set; }

		/// <summary>
		/// Cache sync object
		/// </summary>
		public object SyncObj = new object();

		/// <summary>
		/// Flag whether value loaded
		/// </summary>
		public volatile bool Loaded;

		/// <summary>
		/// Cache entry absolute timeout
		/// </summary>
		public TimeSpan Timeout = SPServices.Cache.DefaultTimeout;

		/// <summary>
		/// Cache entry sliding expiration
		/// </summary>
		public TimeSpan SlidingExpiration = SPServices.Cache.NoSlidingExpiration;

		/// <summary>
		/// Cache entry priority
		/// </summary>
		public CacheItemPriority Priority = SPServices.Cache.DefaultPriority;

		/// <summary>
		/// Default value for cache entry
		/// </summary>
		public TValueType Default;

		/// <summary>
		/// Cached value
		/// </summary>
		public CacheEntryValue<TValueType> Cache;

		public bool SiteDependent { get; set; }
		public bool WebDependent { get; set; }
		public bool UserDependent { get; set; }
		public bool UrlDependent { get; set; }

		/// <summary>
		/// Checks whether sliding expiration enforced
		/// </summary>
		public bool IsSlidingExpiration
		{
			get { return SlidingExpiration.TotalSeconds > 1; }
		}

		/// <summary>
		/// Cache entry in SharePoint cache. Multi-thread, Multi-Core aware.
		/// </summary>
		public CacheEntry(string key)
		{
			PreKeys = new object[] { key };
			PostKeys = null;

			SiteDependent = true;
			WebDependent = false;
			UserDependent = false;
			UrlDependent = false;
		}

		/// <summary>
		/// Cache entry in SharePoint cache. Multi-thread, Multi-Core aware.
		/// </summary>
		public CacheEntry(string prefix, string key)
		{
			PreKeys = new object[] { prefix, key };
			PostKeys = null;

			SiteDependent = true;
			WebDependent = false;
			UserDependent = false;
			UrlDependent = false;
		}

		protected string FullKey;

		public string GetFullKey()
		{
			return
				FullKey ??
				(FullKey = SPServices.Cache.GetKey(PreKeys, SiteDependent, WebDependent, UserDependent, UrlDependent, null, PostKeys));
		}

		protected virtual TValueType GetOrLoadDefault()
		{
			return Default;
		}

		public void EnsureValue()
		{
			lock (SyncObj)
			{
				if (!Loaded)
				{
					Loaded = true;

					var lV = (CacheEntryValue<TValueType>)SPServices.Cache.Get(GetFullKey(), null);

					if (lV == null || lV.IsExpired)
					{
						Cache = null;

						if (lV != null && lV.IsExpired)
						{
							//Loaded = false;
							RemoveFromCache();
						}
					}
					else
					{
						Cache = lV;
					}
				}

				if (Cache != null && Cache.IsExpired)
				{
					//Loaded = false;
					Cache = null;

					RemoveFromCache();
					//Value = Default;
				}
			}
		}

		public TValueType GetOrLoad(Func<TValueType> loader)
		{
			lock (SyncObj)
			{
				EnsureValue();

				if (Cache == null)
					Cache = new CacheEntryValue<TValueType>(DateTime.Now, DateTime.Now.Add(Timeout), loader());

				return Cache.Value;
			}
		}

		public TValueType Value
		{
			get
			{
				lock (SyncObj)
				{
					EnsureValue();

					return (Cache == null ? GetOrLoadDefault() : Cache.Value);
				}
			}
			set
			{
				lock (SyncObj)
				{
					Loaded = true;

					if (Cache == null)
					{
						Cache = new CacheEntryValue<TValueType>(DateTime.Now, DateTime.Now.Add(Timeout), value);
					}
					else
					{
						if (Equals(Default, value))
						{
							//Loaded = false;
							Cache = null;
						}
						else
						{
							Cache.Created = DateTime.Now;
							Cache.Expires = DateTime.Now.Add(Timeout);
							Cache.Value = value;
						}
					}

					SPServices.Cache.Set(GetFullKey(), Cache, Timeout, SlidingExpiration, Priority);
				}
			}
		}

		public bool Exists()
		{
			lock (SyncObj)
			{
				EnsureValue();

				return
					Cache != null && !Cache.IsExpired;
			}
		}

		public void Update()
		{
			lock (SyncObj)
			{
				if (Loaded)
				{
					if (Cache != null && Cache.IsExpired)
					{
						//Loaded = false;
						Cache = null;
					}

					SPServices.Cache.Set(GetFullKey(), Cache, Timeout, SlidingExpiration, Priority);
				}
			}
		}

		public void RemoveFromCache()
		{
			lock (SyncObj)
			{
				Cache = null;

				SPServices.Cache.Remove(GetFullKey());
			}
		}

		public void ClearCache(string keyStartWith)
		{
			RemoveFromCache();

			SPServices.Cache.RemoveKeys(keyStartWith);
		}
	}
}